/*   **********************************************************************  **
 **   Copyright notice                                                       **
 **                                                                          **
 **   (c) 2009, Around(J2)ME				                                 **
 **   All rights reserved.                                                   **
 **                                                                          **
 **   This program and the accompanying materials are made available under   **
 **   the terms of the New BSD License which accompanies this    			 **
 **   distribution.											                 **
 **   A copy is found in the textfile LICENSE.txt							 **
 **                                                                          **
 **   This copyright notice MUST APPEAR in all copies of the file!           **
 **                                                                          **
 **   Main developers:                                                       **
 **     Juri Strumpflohner		http://blog.js-development.com	             **
 **		Matthias Braunhofer		http://matthias.jimdo.com                    **
 **                                                                          **
 **  **********************************************************************  */

package com.aroundj2me.controller;

import java.util.Vector;

import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.List;
import javax.microedition.location.Coordinates;
import javax.microedition.location.LocationException;

import com.aroundj2me.executableTasks.ContinentRetriever;
import com.aroundj2me.executableTasks.GoogleMapsRetriever;
import com.aroundj2me.executableTasks.PlaceFinder;
import com.aroundj2me.location.LocationService;
import com.aroundj2me.location.LocationView;
import com.aroundj2me.main.AroundJ2ME;
import com.aroundj2me.model.ExecutableTask;
import com.aroundj2me.model.Messages;
import com.aroundj2me.model.Place;
import com.aroundj2me.model.TaskProcessListener;
import com.aroundj2me.utility.AlertCreator;
import com.aroundj2me.utility.ImageLoader;
import com.aroundj2me.view.AboutForm;
import com.aroundj2me.view.CategoryListView;
import com.aroundj2me.view.ContinentListView;
import com.aroundj2me.view.IView;
import com.aroundj2me.view.MapView;
import com.aroundj2me.view.PlaceListView;
import com.aroundj2me.view.PlaceView;
import com.aroundj2me.view.ProgressForm;
import com.aroundj2me.view.SearchView;
import com.aroundj2me.xml.XMLDecoder;

//Location Waltherplatz/p.zza walter: 
//lat/lon: 46,4982135 11,354706

public class ApplicationController implements CommandListener {
	private AroundJ2ME parent;
	private Display display;
	private List navList;
	private XMLDecoder decoder;
	private ExecutableTask executableTask;

	// Controllers
	private ScreenController screenController;
	private LocationService locationService;

	// commands
	private Command exitCommand;

	// navigation constants
	private final String RETRIEVE_NEARBY_PLACES = "Nearby places";
	private final String RETRIEVE_PLACES_BY_CAT = "Places by category";
	private final String MANAGE_PLACEDATA = "Manage places data";
	private final String SEARCH_PLACE = "Search nearby place";
	private final String ABOUT = "About";

//	private final String SIMULATOR = "Location Tester";

	// Views
	private CategoryListView categoryListView;
	private ContinentListView continentListView;
	private PlaceListView placeList;
	private PlaceView viewer;
	private SearchView searchView;
	private ProgressForm progressWindow;
	private MapView mapView;
	private AboutForm aboutForm;

	public ApplicationController(AroundJ2ME parent) {
		this.parent = parent;
		this.display = parent.getDisplay();
	}
	
	public void init() {
		createMainMenu();
		createCommands();
		navList.setCommandListener(this);

		screenController = ScreenController.getInstance();
		screenController.setDisplay(this.display);

		progressWindow = new ProgressForm();
	}

	/**
	 * Creates the main menu, basically the navigation list with all its entries
	 * 
	 */
	private void createMainMenu() {
		if (navList == null)
			navList = new List("Around(J2)ME", List.IMPLICIT);

		navList.append(RETRIEVE_NEARBY_PLACES, ImageLoader
				.getImage(ImageLoader.NEARBY_PLACES));
		navList.append(RETRIEVE_PLACES_BY_CAT, ImageLoader
				.getImage(ImageLoader.CATEGORIES));
		navList.append(SEARCH_PLACE, ImageLoader.getImage(ImageLoader.SEARCH));
		navList.append(MANAGE_PLACEDATA, ImageLoader
				.getImage(ImageLoader.MANAGE_PLACEDATA));

		// TODO replace image with appropriate one
		navList.append(ABOUT, ImageLoader.getImage(ImageLoader.ABOUT));
//		navList.append(SIMULATOR, ImageLoader.getImage(ImageLoader.GLOBE));
	}

	/**
	 * Creates the commands
	 * 
	 */
	private void createCommands() {
		this.exitCommand = new Command("Exit", Command.EXIT, 0);
		this.navList.addCommand(exitCommand);
	}	
	
	/**
	 * Kind of factory method that encapsulates the process of reusing/creating
	 * instances of a view (see {@link IView}) This "pattern" avoids repeated
	 * code fragements that 1st check for null and then in case create the
	 * needed object. Moreover it facilitates lazy creation of the views (just
	 * when needed)
	 * 
	 * @param viewName
	 * @return
	 */
	private IView createOrReuseView(String viewName) {
		try {
			Class clazz = Class.forName(viewName);
	
			// check for valid instances
			if (clazz == ContinentListView.class && continentListView != null)
				return continentListView;
			if (clazz == PlaceListView.class && placeList != null)
				return placeList;
			if (clazz == CategoryListView.class && categoryListView != null)
				return categoryListView;
			if (clazz == PlaceView.class && viewer != null)
				return viewer;
			if (clazz == MapView.class && mapView != null)
				return mapView;
			if (clazz == SearchView.class && searchView != null)
				return searchView;
	
			IView view = (IView) clazz.newInstance();
			view.setController(this); // inject controller
			return view;
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	
		return null;
	}

	/**
	 * Creates the correct instance of the taskexecuter or reuses the old
	 * instance if already present
	 * 
	 * @param taskExecutorName
	 *            the name of the class (extends ExecutableTask) to (re)use /
	 *            instantiate
	 * @throws LocationException
	 */
	private ExecutableTask createOrReuseExecutableTask(String taskExecutorName) {
		ExecutableTask resultTask = this.executableTask;
		
		if (taskExecutorName.indexOf("GoogleMapsRetriever") > 0) {
			if (!(this.executableTask instanceof GoogleMapsRetriever))
				resultTask = new GoogleMapsRetriever(this.locationService);
		} else if (taskExecutorName.indexOf("ContinentRetriever") > 0) {
			if (!(this.executableTask instanceof ContinentRetriever))
				resultTask = new ContinentRetriever(parent
						.getAppProperty("serverURL"));
		} else if (taskExecutorName.indexOf("PlaceFinder") > 0) {
			if (!(this.executableTask instanceof PlaceFinder))
				resultTask = new PlaceFinder(this.locationService);
		}
		
		//attach the progress window
		if(resultTask != null)
			resultTask.setProgressWindow(progressWindow);

		return resultTask;
	}

	/**
	 * Action handler
	 */
	public void commandAction(Command c, Displayable d) {
		if (c == exitCommand) {
			ScreenController.destroyInstance();
			this.display.setCurrent((Displayable) null);
			parent.notifyDestroyed();
		} else if (d == navList) {
			// some navigation item clicked
			if (IsCommandSource(RETRIEVE_NEARBY_PLACES)) {
				executeNearbyPlacesRequest();

			} else if (IsCommandSource(RETRIEVE_PLACES_BY_CAT)) {
				categoryListView = (CategoryListView) createOrReuseView(CategoryListView.class
						.getName());
				screenController.display(categoryListView, true);
				
			}else if(IsCommandSource(SEARCH_PLACE)){
				searchView = (SearchView) createOrReuseView(SearchView.class.getName());
				screenController.display(searchView, true);
				
			}else if (IsCommandSource(MANAGE_PLACEDATA)) {

				continentListView = (ContinentListView) createOrReuseView(ContinentListView.class
						.getName());
				continentListView.loadList();
				screenController.display(continentListView, true);

			} else if (IsCommandSource(ABOUT)) {
				aboutForm = new AboutForm();
				screenController.display(aboutForm, true);
				
			} 
//				else if (IsCommandSource(SIMULATOR)) {
//				 LocationView view = new LocationView(locationService);
//				 screenController.display(view, true);
//			}
		}
	}

	private boolean IsCommandSource(String commandIdentifier) {
		return navList.getString(navList.getSelectedIndex()).equals(
				commandIdentifier);
	}

	/**
	 * Calls the place-viewer to display a selected place
	 * 
	 * @param p
	 */
	public void displayPlace(Place p) {
		viewer = (PlaceView) createOrReuseView(PlaceView.class.getName());
		viewer.reset();
		viewer.setPlace(p);
		viewer.displayPlace();
		screenController.display(viewer, true);
	}

	/**
	 * Execute the search
	 * 
	 */
	public void executeSearch() {
		executableTask = createOrReuseExecutableTask(PlaceFinder.class
				.getName());

		if (executableTask != null) {
			executableTask.setProgressMessage("searching for nearby places...");
			
			((PlaceFinder) executableTask).setSearchCriteria(searchView
					.getSearchField().getString());

			executableTask.setListener(new TaskProcessListener() {
				public void onError(String status) {
					showErrorDialog(status);
				}

				public void taskFinished(Object result) {
					Vector places = (Vector) result;

					placeList = (PlaceListView) createOrReuseView(PlaceListView.class
							.getName());

					if (places != null && places.size() > 0) {
						placeList.reset();
						placeList.setPlaces(places);
						placeList.loadList();
						screenController
								.display(placeList.getPlaceList(), true);
					} else {
						showInfoDialog(Messages.DISPLAY_PLACES_NOTHING_FOUND,
								false);
					}
				}
			});

			executeExecutableTaskWithinThread();
		}
	}

	/**
	 * Executes a search for nearby places without any specific restriction regarding categories
	 */
	public void executeNearbyPlacesRequest() {
		executableTask = createOrReuseExecutableTask(PlaceFinder.class
				.getName());

		if (executableTask != null) {
			executableTask.setProgressMessage("Searching for nearby pleaces...");
			
			((PlaceFinder) executableTask).setCategory(null);
			
			executableTask.setListener(new TaskProcessListener() {
				public void onError(String status) {
					showErrorDialog(status);
				}

				public void taskFinished(Object result) {
					Vector places = (Vector) result;

					placeList = (PlaceListView) createOrReuseView(PlaceListView.class
							.getName());

					if (places != null && places.size() > 0) {
						placeList.reset();
						placeList.setPlaces(places);
						placeList.loadList();
						screenController
								.display(placeList.getPlaceList(), true);
						
					} else {
						showInfoDialog(Messages.DISPLAY_PLACES_NOTHING_FOUND,
								false);
					}
				}
			});

			executeExecutableTaskWithinThread();
		}
	}

	/**
	 * Executes a search for nearby locations according that match a given 
	 * category (chosen by the user)
	 */
	public void executeCategoryRequest() {

		executableTask = createOrReuseExecutableTask(PlaceFinder.class
				.getName());

		if (executableTask != null) {
			String category = categoryListView.getSelectedCategory();
			executableTask.setProgressMessage("Retrieving places for category " + category + "...");
			
			((PlaceFinder) executableTask).setCategory(category);

			executableTask.setListener(new TaskProcessListener() {
				public void onError(String status) {
					showErrorDialog(status);
				}

				public void taskFinished(Object result) {
					Vector places = (Vector) result;

					placeList = (PlaceListView) createOrReuseView(PlaceListView.class
							.getName());

					if (places != null && places.size() > 0) {
						placeList.reset();
						placeList.setPlaces(places);
						placeList.loadList();
						screenController
								.display(placeList.getPlaceList(), true);
					} else {
						showInfoDialog(Messages.DISPLAY_PLACES_NOTHING_FOUND,
								false);
					}
				}
			});

			executeExecutableTaskWithinThread();
		}
	}

	/**
	 * 
	 */
	public void executeContinentRequest() {
		if (continentListView != null) {
			final String continentToRetrieve = continentListView
					.getSelectedContinent();

			executableTask = createOrReuseExecutableTask(ContinentRetriever.class
					.toString());

			if (executableTask != null) {
				executableTask.setProgressMessage("retrieving places for " + continentToRetrieve
						+ "...");

				((ContinentRetriever) executableTask)
						.setContinent(continentToRetrieve);

				executableTask.setListener(new TaskProcessListener() {
					public void onError(String status) {
						showErrorDialog(Messages.SERVER_CONNECTION_FAILED);
					}
				
					public void taskFinished(Object result) {
						if (decoder == null) {
							decoder = new XMLDecoder();
						}
				
						if (decoder != null) {
							decoder.setXmlResponseString(result.toString());
				
							try {
								decoder.parseXML();
				
								locationService.createLandmarkStore(continentToRetrieve);
				
								Vector places = decoder.getPlaces();
								for (int i = 0; i < places.size(); i++) {
									Place place = (Place) places.elementAt(i);
									locationService.addLandmark(
											continentToRetrieve, place
													.getLandmark(), place
													.getCategories());
								}
								
								showInfoDialog(Messages.INSTALLATION_SUCCESS, false);
							} catch (Exception ex) {
								showErrorDialog(Messages.INSTALLATION_ERROR);
							}
						}
				
						continentListView.loadList();
					}
				});

				executeExecutableTaskWithinThread();
			} else {
				showErrorDialog(Messages.INSTALLATION_ERROR);
			}
		}
	}

	/**
	 * Makes a request to the Google Static Map API to retrieve the map with the
	 * user's current position and the place he's interested in
	 * 
	 * @param pointOfInterest
	 *            the place the user is interested in
	 */
	public void executeGoogleMapsRequest(final boolean loadMapView) {
		Coordinates coordToDisplay = viewer.getPlace().getLandmark()
				.getQualifiedCoordinates();

		mapView = (MapView) createOrReuseView(MapView.class.getName());

		executableTask = createOrReuseExecutableTask(GoogleMapsRetriever.class
				.toString());

		if (executableTask != null) {
			executableTask.setProgressMessage("loading map...");

			((GoogleMapsRetriever) executableTask)
					.setPointOfInterest(coordToDisplay);
			((GoogleMapsRetriever) executableTask).setWidth(mapView.getWidth());
			((GoogleMapsRetriever) executableTask).setHeight(mapView
					.getHeight());
			int zoomLevel = mapView.getZoomLevel();
			// just set the zoomlevel if it is bigger than -1, otherwise the
			// default one will be used
			// adjusting the map to have both points on it
			if (zoomLevel > -1)
				((GoogleMapsRetriever) executableTask).setZoomLevel(zoomLevel);
			((GoogleMapsRetriever) executableTask).setMapType(mapView
					.getMapType());

			executableTask.setListener(new TaskProcessListener() {
				public void onError(String status) {
					showErrorDialog(status);
				}

				public void taskFinished(Object result) {
					mapView.showMap((Image) result);
					if (loadMapView)
						screenController.display(mapView, true);
				}
			});

			executeExecutableTaskWithinThread();
		} else {
			showErrorDialog(Messages.GOOGLEMAP_ERROR);
		}
	}

	/**
	 * Utility method for displaying an error alert to the user
	 * 
	 * @param message
	 */
	private void showErrorDialog(String message) {
		screenController.display(AlertCreator.getErrorAlert(message));
	}

	/**
	 * Utility method for displaying an info alert to the user
	 * 
	 * @param message
	 * @param userHasToClick
	 */
	private void showInfoDialog(String message, boolean userHasToClick) {
		screenController.display(AlertCreator.getInfoAlert(message,
				userHasToClick));
	}

	/**
	 * Executes the current instantiated executable task (see {@link ExecutableTask}
	 * within a separate thread.
	 */
	private void executeExecutableTaskWithinThread() {
		Thread t = new Thread(executableTask);
		t.start();
	}

	/*
	 * Getters and Setters
	 */

	public LocationService getLocationService() {
		return locationService;
	}

	public void setLocationService(LocationService locationService) {
		this.locationService = locationService;
	}

	public List getNavList() {
		return navList;
	}

}
